package com.gin.ehdownloader.service;

import com.gin.api.EhApi;
import com.gin.aria2.dto.base.Aria2Option;
import com.gin.aria2.dto.form.Aria2AddUriForm;
import com.gin.aria2.enums.TaskStatus;
import com.gin.aria2.exception.Aria2RequestException;
import com.gin.aria2.main.Aria2Api;
import com.gin.aria2.response.result.Aria2GlobalOption;
import com.gin.aria2.response.result.Aria2GlobalStatus;
import com.gin.aria2.response.result.Aria2TaskStatus;
import com.gin.ehdownloader.datafile.ConfigDataFile;
import com.gin.ehdownloader.datafile.TaskDataFile;
import com.gin.ehdownloader.entity.EhDownloadTask;
import com.gin.ehdownloader.utils.FileIoUtils;
import com.gin.ehdownloader.utils.ListUtils;
import com.gin.ehdownloader.utils.StrUtils;
import com.gin.entity.Gallery;
import com.gin.entity.GalleryImageThumbnail;
import com.gin.entity.ImageLimit;
import com.gin.utils.GalleryIdTag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.net.ssl.SSLHandshakeException;
import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * EH服务
 * @author : ginstone
 * @version : v1.0.0
 * @since : 2023/3/13 16:00
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class EhService {
    /**
     * 文件名非法字符
     */
    public static final Pattern ILLEGAL_PATTERN = Pattern.compile("[\\\\/:\\*\\?\\\"<>\\|]");
    /**
     * 限额阈值
     */
    public static final int LIMIT_THRESHOLD = 90;
    private final EhApi ehApi;
    private final Aria2Api aria2Api;

    private final ConfigDataFile configDataFile;
    private final TaskDataFile taskDataFile;

    private boolean addingQueue = false;
    /**
     * 限额警告
     */
    private boolean limitWarning = false;

    /**
     * 删除未下载完成的文件
     * @param errorTasks 出错的任务
     */
    private static void deleteTaskFiles(List<Aria2TaskStatus> errorTasks) {
        //noinspection ResultOfMethodCallIgnored
        errorTasks.stream()
                .flatMap(t -> t.getFilePath().stream())
                .flatMap(p -> Stream.of(p, p + ".aria2"))
                .map(File::new)
                .filter(File::exists)
                .forEach(File::delete);
    }

    private static String getFilename(String imagePageUrl, String originalUrl) {
        // 图片在画廊中的序号
        final int index = Integer.parseInt(imagePageUrl.substring(imagePageUrl.lastIndexOf("-") + 1));
        // 下载文件名
        final String prefix = String.format("%04d_", index);

        final int begin = originalUrl.lastIndexOf("/") + 1;
        final int end = originalUrl.contains("?") ? originalUrl.indexOf("?") : originalUrl.length();
        return prefix + originalUrl.substring(begin, end);
    }

    /**
     * 下载画廊的指定范围的图片
     * @param url   画廊url
     * @param range 范围
     */
    public void addTask(String url, String range) throws IOException, InterruptedException {
        addTask(url, StrUtils.parseRange(range));
    }

    /**
     * 下载画廊的指定范围的图片
     * @param url     画廊url
     * @param indexes 序号
     */
    public void addTask(String url, List<Integer> indexes) throws IOException, InterruptedException {
        final Gallery gallery = gallery(url);
        // 需要下载的图片详情页地址
        final List<String> imageUrls = ListUtils.filterByIndex(gallery.getThumbnails().stream().map(GalleryImageThumbnail::getImagePageUrl).toList(),
                                                               indexes);
        // 添加任务队列
        final Matcher matcher = ILLEGAL_PATTERN.matcher(gallery.getTitle());
        final String title = matcher.replaceAll("_");

        taskDataFile.addTasks(configDataFile.getDir() + File.separator + title, imageUrls);
    }

    /**
     * 请求一个画廊数据, 如果缓存文件中有，直接使用
     * @param url 画廊地址
     * @return 画廊
     */
    public Gallery gallery(String url) throws IOException, InterruptedException {
        final GalleryIdTag galleryIdTag = new GalleryIdTag(url);
        final File file = new File(String.format("./galleries/%d_%s.json", galleryIdTag.getId(), galleryIdTag.getTag()));
        if (file.exists()) {
            return FileIoUtils.readObj(file, Gallery.class);
        }
        final Gallery gallery = ehApi.getGallery(url);
        FileIoUtils.writeObj(file, gallery);
        return gallery;
    }

    /**
     * 定时提交未开始的任务
     */
    @Scheduled(cron = "0/3 * * * * ?")
    public void scheduledAddQueue() throws IOException, Aria2RequestException {
        if (addingQueue || limitWarning) {
            return;
        }
        final EhDownloadTask task = taskDataFile.findTaskWithNullGid();
        if (task == null) {
            return;
        }
        final Aria2GlobalOption globalOption = aria2Api.getGlobalOption().sync();
        final Aria2GlobalStatus globalStatus = aria2Api.getGlobalStat().sync();
        if (globalStatus.getNumActive() >= globalOption.getMaxConcurrentDownloads()) {
            // 检查aria2下载队列已满，不执行操作
            return;
        }
        // 开始请求原图地址

        this.addingQueue = true;
        try {
            // 图片详情页地址
            final String imagePageUrl = task.getImagePageUrl();
            // 图片原图地址
            final String originalUrl = ehApi.getOriginalUrl(imagePageUrl);

            final Aria2Option params = new Aria2Option();
            params.setDir(task.getDir());
            params.setContinues(false);
            params.setOut(getFilename(imagePageUrl, originalUrl));
            final String gid = aria2Api.addUri(new Aria2AddUriForm(Collections.singleton(originalUrl), params)).sync();
            taskDataFile.onTaskAddSuccess(imagePageUrl, gid);
        } catch (SocketTimeoutException | SSLHandshakeException timeoutException) {
            log.warn(timeoutException.getLocalizedMessage());
        } catch (IOException e) {
            log.warn(e.getMessage());
            this.limitWarning = true;
        } finally {
            this.addingQueue = false;
        }
    }

    /**
     * 定时检查限额
     */
    @Scheduled(cron = "30/60 * * * * ?")
    public void scheduledCheckLimit() throws IOException {
        try {
            final ImageLimit imageLimit = ehApi.getImageLimit();
            if (imageLimit != null) {
                final Integer current = imageLimit.getCurrent();
                final Integer max = imageLimit.getMax();
                final int percent = current * 100 / max;
                if (percent > LIMIT_THRESHOLD) {
                    log.warn("限额接近阈值: {} / {}", current, max);
                    limitWarning = true;
                } else {
                    log.info("图像限额: {} / {}", current, max);
                    limitWarning = false;
                }
            }
        } catch (SocketTimeoutException | SSLHandshakeException timeoutException) {
            log.warn(timeoutException.getLocalizedMessage());
        }
    }

    /**
     * 检查丢失的任务
     */
    @Scheduled(cron = "40/60 * * * * ?")
    public void scheduledCheckLostTask() throws Aria2RequestException, IOException {
        // 本程序管理的任务gid
        final List<String> gidList = new ArrayList<>(taskDataFile.getGidList());
        // aria2中现存任务的gid
        final List<String> taskGid = aria2Api.tellAll(1, 100).sync().stream().flatMap(Collection::stream).flatMap(Collection::stream).map(
                Aria2TaskStatus::getGid).toList();

        gidList.removeAll(taskGid);

        // 如果存在丢失的任务，重置任务状态
        if (!gidList.isEmpty()) {
            taskDataFile.clearGid(gidList);
        }
    }

    @Scheduled(cron = "3/10 * * * * ?")
    public void scheduledCheckStop() throws Aria2RequestException, IOException {
        // 本程序管理的任务gid
        final List<String> gidList = taskDataFile.getGidList();
        // 已经停止的任务
        final List<Aria2TaskStatus> stopTasks = aria2Api.tellStop(1, 100).sync().stream().filter(t -> gidList.contains(t.getGid())).toList();
        if (stopTasks.isEmpty()) {
            return;
        }
        // 处理错误8的任务
        handleError8(stopTasks.stream().filter(t -> t.getErrorCode() == 8).toList());
        // 处理下载成功的任务
        handleSuccess(stopTasks.stream().filter(t -> t.getStatus() == TaskStatus.complete && t.getTotalLength() != 15).toList());
        // 处理其他错误的任务
        handleOtherError(stopTasks.stream().filter(t -> t.getStatus() == TaskStatus.error || t.getTotalLength() == 15).toList());
    }

    /**
     * 处理错误8的任务
     * @param list 任务列表
     */
    private void handleError8(List<Aria2TaskStatus> list) throws Aria2RequestException, IOException {
        if (list.isEmpty()) {
            return;
        }
        // 删除文件
        deleteTaskFiles(list);
        // 重试任务
        final HashMap<String, String> map = aria2Api.retryTask(list);
        // 更新gid
        taskDataFile.updateGid(map);
        log.info("重试 {} 个任务", map.size());
    }

    /**
     * 处理其他错误的任务
     * @param list 任务列表
     */
    private void handleOtherError(List<Aria2TaskStatus> list) throws IOException {
        if (list.isEmpty()) {
            return;
        }
        final List<String> gid = list.stream().map(Aria2TaskStatus::getGid).toList();
        // 删除文件
        deleteTaskFiles(list);
        // 删除任务
        aria2Api.removeDownloadResult(gid);
        // 重置任务状态
        taskDataFile.clearGid(gid);

        log.info("重置 {} 个任务的状态", gid.size());
    }

    /**
     * 处理下载成功的任务
     * @param list 任务
     */
    private void handleSuccess(List<Aria2TaskStatus> list) throws Aria2RequestException, IOException {
        if (list.isEmpty()) {
            return;
        }
        final List<String> gid = list.stream().map(Aria2TaskStatus::getGid).toList();
        // 移除任务
        aria2Api.removeDownloadResult(gid).sync();
        // 移除队列
        taskDataFile.onTaskCompleted(gid);
        log.info("移除 {} 个下载成功任务", gid.size());
    }
}
